abstract class BoundedLocalCache<K, V> extends BLCHeader.DrainStatusRef<K, V> implements LocalCache<K, V> {
    /**
      *
      */
    void maintenance(@Nullable Runnable task) {
        lazySetDrainStatus(PROCESSING_TO_IDLE);

        try {
            drainReadBuffer();
            drainWriteBuffer();

            if (task != null) {
            task.run();
            }

            drainKeyReferences();
            drainValueReferences();

            expireEntries();
            evictEntries();
            
            climb();
        } finally {
            if ((drainStatus() != PROCESSING_TO_IDLE) || !casDrainStatus(PROCESSING_TO_IDLE, IDLE)) {
                lazySetDrainStatus(REQUIRED);
            }
        }
    }

    /**
      * 清理已被垃圾回收的弱引用key
      */
    void drainKeyReferences() {
        // 如果没有使用弱引用的key，那么不用处理
        if (!collectKeys()) {
            return;
        }

        // 所有弱引用的key在被垃圾回收之后都存放在了一个队列中
        Reference<? extends K> keyRef;
        while ((keyRef = keyReferenceQueue().poll()) != null) {
            Node<K, V> node = data.get(keyRef);
            if (node != null) {
                evictEntry(node, RemovalCause.COLLECTED, 0L);
            }
        }
    }

    /**
      * 清理已被垃圾回收的软弱引用value
      */
    void drainValueReferences() {
        // 如果没有使用软弱引用的value，那么不用处理
        if (!collectValues()) {
            return;
        }

        // 所有软弱引用的value在被垃圾回收之后都存放在了一个队列中
        Reference<? extends V> valueRef;
        while ((valueRef = valueReferenceQueue().poll()) != null) {
            // 在原生Java接口Reference的基础上，caffeine继承实现了InternalReference接口，增加了获取value对应key的功能
            // 故在封装value为引用时，就同时持有其key
            InternalReference<V> ref = (InternalReference<V>) valueRef;
            Node<K, V> node = data.get(ref.getKeyReference());
            if (node != null && valueRef == node.getValueReference()) {
                evictEntry(node, RemovalCause.COLLECTED, 0L);
            }
        }
    }

    /**
      * 清理过期数据
      */
    void expireEntries() {
        long now = expirationTicker().read();
        expireAfterAccessEntries(now);
        expireAfterWriteEntries(now);
        expireVariableEntries(now);

        Pacer pacer = pacer();
        if (pacer != null) {
            long delay = getExpirationDelay(now);
            if (delay != Long.MAX_VALUE) {
                pacer.schedule(executor, drainBuffersTask, now, delay);
            }
        }
  }

    /**
      * 淘汰数据，使用了W-TinyLFU算法
      */
    void evictEntries() {
        // 如果没有配置最大缓存项数量或最大比重
        if (!evicts()) {
            return;
        }

        // 从window区域剔除缓存项
        int candidates = evictFromWindow();
        evictFromMain(candidates);
    }

    /**
      * 将缓存项从Window区域转移到Main区域的Probation队列，以保证Window区域不会超出该区域的最大限制
      * @return 转移的缓存项个数
      */
    int evictFromWindow() {
        int candidates = 0;
        // @a
        while (Node<K, V> node = accessOrderWindowDeque().peek(); windowWeightedSize() > windowMaximum() && node != null; node = node.getNextInAccessOrder()) {
            if (node.getPolicyWeight() != 0) {
                node.makeMainProbation();
                accessOrderWindowDeque().remove(node);
                accessOrderProbationDeque().add(node);
                candidates++;
                setWindowWeightedSize(windowWeightedSize() - node.getPolicyWeight());
            }
        }

        return candidates;
    }

    void evictFromMain(int candidates) {
        int victimQueue = PROBATION;
        Node<K, V> victim = accessOrderProbationDeque().peekFirst();
        Node<K, V> candidate = accessOrderProbationDeque().peekLast();
        while (weightedSize() > maximum()) {
            if (candidates == 0) {
                candidate = null;
            }

            if (candidate == null && victim == null) {
                if (victimQueue == PROBATION) {
                    victim = accessOrderProtectedDeque().peekFirst();
                    victimQueue = PROTECTED;
                    continue;
                } else if (victimQueue == PROTECTED) {
                    victim = accessOrderWindowDeque().peekFirst();
                    victimQueue = WINDOW;
                    continue;
                }

                break;
            }

            if (victim != null && victim.getPolicyWeight() == 0) {
                victim = victim.getNextInAccessOrder();
                continue;
            } else if (candidate != null && candidate.getPolicyWeight() == 0) {
                candidate = candidate.getPreviousInAccessOrder();
                candidates--;
                continue;
            }

            if (victim == null) {
                Node<K, V> previous = candidate.getPreviousInAccessOrder();
                Node<K, V> evict = candidate;
                candidate = previous;
                candidates--;
                evictEntry(evict, RemovalCause.SIZE, 0L);
                continue;
            } else if (candidate == null) {
                Node<K, V> evict = victim;
                victim = victim.getNextInAccessOrder();
                evictEntry(evict, RemovalCause.SIZE, 0L);
                continue;
            }

            K victimKey = victim.getKey();
            K candidateKey = candidate.getKey();
            if (victimKey == null) {
                Node<K, V> evict = victim;
                victim = victim.getNextInAccessOrder();
                evictEntry(evict, RemovalCause.COLLECTED, 0L);
                continue;
            } else if (candidateKey == null) {
                candidates--;
                Node<K, V> evict = candidate;
                candidate = candidate.getPreviousInAccessOrder();
                evictEntry(evict, RemovalCause.COLLECTED, 0L);
                continue;
            }

            if (candidate.getPolicyWeight() > maximum()) {
                candidates--;
                Node<K, V> evict = candidate;
                candidate = candidate.getPreviousInAccessOrder();
                evictEntry(evict, RemovalCause.SIZE, 0L);
                continue;
            }
            
            candidates--;
            if (admit(candidateKey, victimKey)) {
                Node<K, V> evict = victim;
                victim = victim.getNextInAccessOrder();
                evictEntry(evict, RemovalCause.SIZE, 0L);
                candidate = candidate.getPreviousInAccessOrder();
            } else {
                Node<K, V> evict = candidate;
                candidate = candidate.getPreviousInAccessOrder();
                evictEntry(evict, RemovalCause.SIZE, 0L);
            }
        }
    }
}

